package net.csdn.business.mirror.utils;

import com.alibaba.fastjson.JSONException;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.*;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.entity.mime.ByteArrayBody;
import org.apache.hc.client5.http.entity.mime.ContentBody;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.HttpClientHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.Timeout;
import org.apache.http.client.config.CookieSpecs;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class HttpUtil {

    @Data
    public static class Response {
        /**httpstatus**/
        private int code;
        private String content;
        private Map<String, String> headers;

        public Response(String content) {
            this.content = content;
        }

        public Response(String content,int code) {
            this.content = content;
            this.code=code;
        }

        public Response(String content, Map<String, String> headers) {
            this.content = content;
            this.headers = headers;
        }

        public Response(String content, Map<String, String> headers,int code) {
            this.content = content;
            this.headers = headers;
            this.code=code;
        }
    }


    public static final long IGNORE_TIMEOUT = -1;

    public static final Map<String, String> JSON;

    public static final Map<String, String> FORM;

    public static final String COOKIE = "Cookie";

    public final static ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai");

    public static final String DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";

    static {
        JSON = ImmutableMap.of("content-type", ContentType.JSON.getContent());
        FORM = ImmutableMap.of("content-type", ContentType.FORM.getContent());
    }

    private HttpUtil() {
    }

    /**
     * 支持各种http请求调用
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param cookies 请求cookie
     * @param content 请求参数
     * @return
     */
    public static Response httpRequest(RequestMethodEnum requestMethodEnum, String url, Map<String, String> headers, String cookies, String content) {
        return httpRequest(requestMethodEnum, url, headers, cookies, content, 500);
    }

    public static Response httpRequest(RequestMethodEnum requestMethodEnum, String url, Map<String, String> headers, String cookies, String content, long timeout) {
        ContentType contentType = ContentType.NONE;
        if (!CollectionUtils.isEmpty(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                if (entry.getValue().contains(ContentType.FORM.getContent())) {
                    contentType = ContentType.FORM;
                    break;
                }
                if (entry.getValue().contains(ContentType.JSON.getContent())) {
                    contentType = ContentType.JSON;
                    break;
                }
            }
        }
        switch (requestMethodEnum) {
            case DELETE:
                return delete(url, headers, cookies, content, timeout);
            case PATCH:
                return patch(url, headers, cookies, content, contentType, timeout);
            case POST:
                return post(url, headers, cookies, content, contentType, timeout);
            case PUT:
                return put(url, headers, cookies, content, contentType, timeout);
            case GET:
                return get(url, headers, cookies, content, timeout);
            default:
                throw new IllegalArgumentException("未知的HTTP请求方式");
        }
    }

    /**
     * 设置路由策略 可以根据不同
     *
     * @param httpMethod
     * @param url
     * @param <T>
     * @return
     */
    private static <T extends HttpUriRequestBase> T configRouteStrategy(T httpMethod, String url) {
        if (url.contains("axsxsxsxs")) {
            httpMethod.setConfig(getCustomRequestConfig(1000, 1000));
        } else {
            httpMethod.setConfig(getCustomRequestConfig(200, 200));
        }
        return httpMethod;
    }

    /**
     * 设置超时策略
     *
     * @param httpMethod
     * @param timeOut    配置超时时间 默认500ms
     * @param <T>
     * @return
     */
    private static <T extends HttpUriRequestBase> T configTimeOutStrategy(T httpMethod, long timeOut) {
        if (timeOut <= 0) {
            return httpMethod;
        }
        httpMethod.setConfig(getCustomRequestConfig(timeOut, timeOut));
        return httpMethod;
    }

    /**
     * 设置请求参数
     *
     * @return
     */
    private static RequestConfig getCustomRequestConfig(long connectTimeout, long requestTimeout) {
        RequestConfig.Builder builder = RequestConfig.custom();
        builder.setConnectTimeout(Timeout.ofMilliseconds(connectTimeout))
                .setConnectionRequestTimeout(Timeout.ofMilliseconds(requestTimeout))
                .setResponseTimeout(Timeout.ofMilliseconds(connectTimeout))
                .setCookieSpec(CookieSpecs.STANDARD_STRICT);
        return builder.build();
    }

    /**
     * 发送http post application/json
     *
     * @param url         请求地址
     * @param headerMap   请求头参数
     * @param cookie      cookie
     * @param jsonContent 请求参数
     * @param contentType content-type
     * @return
     */
    private static Response post(String url, Map<String, String> headerMap, String cookie, String jsonContent, ContentType contentType, long timeOut) {
        HttpPost httpPost = null;
        try (CloseableHttpClient httpClient = getHttpClient()) {
            if (contentType.equals(ContentType.NONE)) {
                url = getUrl(url, jsonContent);
            }
            httpPost = new HttpPost(url);
            if (!CollectionUtils.isEmpty(headerMap)) {
                for (Map.Entry<String, String> next : headerMap.entrySet()) {
                    httpPost.addHeader(next.getKey(), next.getValue());
                }
            }
            if (StringUtils.isNotBlank(cookie)) {
                httpPost.setHeader(COOKIE, cookie);
            }
            if (StringUtils.isNotBlank(jsonContent) && contentType.equals(ContentType.JSON)) {
                StringEntity entity = new StringEntity(jsonContent, StandardCharsets.UTF_8);
                httpPost.setEntity(entity);
            }
            if (contentType.equals(ContentType.FORM)) {
                List<NameValuePair> pairs = getNameValuePair(jsonContent);
                if (!CollectionUtils.isEmpty(pairs)) {
                    httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8));
                }
            }
            configTimeOutStrategy(httpPost, timeOut);
            CloseableHttpResponse response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            String res = EntityUtils.toString(entity);
            int code=response.getCode();
            log.info("process post request, response Http Status：【{}】", code);
            if (code != HttpStatus.SC_OK) {
                log.error("处理[POST]请求失败，请求接口：【{}】,请求参数：【{}】，返回状态：【{}】，返回内容：【{}】", url, jsonContent, response.getReasonPhrase(), res);
            }
            return new Response(res,code);
        } catch (Exception e) {
            if (null != httpPost) {
                httpPost.abort();
            }
            throw new RuntimeException("HTTP请求失败:" + e.getMessage());
        }
    }

    /**
     * http get 请求
     *
     * @param url
     * @param headerMap
     * @param cookie
     * @param content
     * @return
     */
    private static Response get(String url, Map<String, String> headerMap, String cookie, String content, long timeOut) {
        HttpGet httpGet = null;
        try (CloseableHttpClient httpClient = getHttpClient()) {
            url = getUrl(url, content);
            httpGet = new HttpGet(url);
            if (!CollectionUtils.isEmpty(headerMap)) {
                for (Map.Entry<String, String> next : headerMap.entrySet()) {
                    httpGet.addHeader(next.getKey(), next.getValue());
                }
            }
            if (StringUtils.isNotBlank(cookie)) {
                httpGet.setHeader(COOKIE, cookie);
            }
            configTimeOutStrategy(httpGet, timeOut);
            CloseableHttpResponse response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            String res = EntityUtils.toString(entity);
            int code=response.getCode();
            log.info("process get request, response Http Status：【{}】", code);
            if (code != HttpStatus.SC_OK) {
                log.error("处理[GET]请求失败，请求接口：【{}】,请求参数：【{}】，返回状态：【{}】，返回内容：【{}】", url, content, response.getReasonPhrase(), res);
            }
            Map<String, String> headers = Arrays.stream(response.getHeaders()).collect(Collectors.toMap(Header::getName, Header::getValue, (header1, header2) -> header2));
            return new Response(res, headers,code);
        } catch (Exception e) {
            if (null != httpGet) {
                httpGet.abort();
            }
            throw new RuntimeException("HTTP请求失败:" + e.getMessage());
        }
    }

    /**
     * http put 请求
     *
     * @param url
     * @param headerMap
     * @param cookie
     * @param content
     * @param contentType
     * @return
     */
    private static Response put(String url, Map<String, String> headerMap, String cookie, String content, ContentType contentType, long timeOut) {
        HttpPut httPut = null;
        try (CloseableHttpClient httpClient = getHttpClient()) {
            if (contentType.equals(ContentType.NONE)) {
                url = getUrl(url, content);
            }
            httPut = new HttpPut(url);
            if (!CollectionUtils.isEmpty(headerMap)) {
                for (Map.Entry<String, String> next : headerMap.entrySet()) {
                    httPut.addHeader(next.getKey(), next.getValue());
                }
            }
            if (StringUtils.isNotBlank(cookie)) {
                httPut.setHeader(COOKIE, cookie);
            }
            if (StringUtils.isNotBlank(content) && contentType.equals(ContentType.JSON)) {
                StringEntity entity = new StringEntity(content, StandardCharsets.UTF_8);
                httPut.setEntity(entity);
            }
            if (contentType.equals(ContentType.FORM)) {
                List<NameValuePair> pairs = getNameValuePair(content);
                if (!CollectionUtils.isEmpty(pairs)) {
                    httPut.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8));
                }
            }
            configTimeOutStrategy(httPut, timeOut);
            CloseableHttpResponse response = httpClient.execute(httPut);
            HttpEntity entity = response.getEntity();
            String res = EntityUtils.toString(entity);
            int code=response.getCode();
            log.info("process put request, response Http Status：【{}】", code);
            if (code != HttpStatus.SC_OK) {
                log.error("处理[PUT]请求失败，请求接口：【{}】,请求参数：【{}】，返回状态：【{}】，返回内容：【{}】", url, content, response.getReasonPhrase(), res);
            }
            return new Response(res,code);
        } catch (Exception e) {
            if (null != httPut) {
                httPut.abort();
            }
            throw new RuntimeException("HTTP请求失败:" + e.getMessage());
        }
    }

    /**
     * http patch 请求
     *
     * @param url
     * @param headerMap
     * @param cookie
     * @param content
     * @param contentType
     * @return
     */
    private static Response patch(String url, Map<String, String> headerMap, String cookie, String content, ContentType contentType, long timeOut) {
        HttpPatch httpPatch = null;
        try (CloseableHttpClient httpClient = getHttpClient()) {
            if (contentType.equals(ContentType.NONE)) {
                url = getUrl(url, content);
            }
            httpPatch = new HttpPatch(url);
            if (!CollectionUtils.isEmpty(headerMap)) {
                for (Map.Entry<String, String> next : headerMap.entrySet()) {
                    httpPatch.addHeader(next.getKey(), next.getValue());
                }
            }
            if (StringUtils.isNotBlank(cookie)) {
                httpPatch.setHeader(COOKIE, cookie);
            }
            if (StringUtils.isNotBlank(content) && contentType.equals(ContentType.JSON)) {
                StringEntity entity = new StringEntity(content, StandardCharsets.UTF_8);
                httpPatch.setEntity(entity);
            }
            if (contentType.equals(ContentType.FORM)) {
                List<NameValuePair> pairs = getNameValuePair(content);
                if (!CollectionUtils.isEmpty(pairs)) {
                    httpPatch.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8));
                }
            }
            configTimeOutStrategy(httpPatch, timeOut);
            CloseableHttpResponse response = httpClient.execute(httpPatch);
            HttpEntity entity = response.getEntity();
            String res = EntityUtils.toString(entity);
            int code=response.getCode();
            log.info("process patch request, response Http Status：【{}】", code);
            if (code != HttpStatus.SC_OK) {
                log.error("处理[PATCH]请求失败，请求接口：【{}】,请求参数：【{}】，返回状态：【{}】，返回内容：【{}】", url, content, response.getReasonPhrase(), res);
            }
            return new Response(res,code);
        } catch (Exception e) {
            if (null != httpPatch) {
                httpPatch.abort();
            }
            throw new RuntimeException("HTTP请求失败:" + e.getMessage());
        }
    }

    /**
     * http delete 请求
     *
     * @param url
     * @param headerMap
     * @param cookie
     * @param content
     * @return
     */
    private static Response delete(String url, Map<String, String> headerMap, String cookie, String content, long timeOut) {
        HttpDelete httpDelete = null;
        try (CloseableHttpClient httpClient = getHttpClient()) {
            url = getUrl(url, content);
            httpDelete = new HttpDelete(url);
            if (!CollectionUtils.isEmpty(headerMap)) {
                for (Map.Entry<String, String> next : headerMap.entrySet()) {
                    httpDelete.addHeader(next.getKey(), next.getValue());
                }
            }
            if (StringUtils.isNotBlank(cookie)) {
                httpDelete.setHeader(COOKIE, cookie);
            }
            configTimeOutStrategy(httpDelete, timeOut);
            CloseableHttpResponse response = httpClient.execute(httpDelete);
            HttpEntity entity = response.getEntity();
            String res = EntityUtils.toString(entity);
            int code=response.getCode();
            log.info("process delete request, response Http Status：【{}】", code);
            if (code != HttpStatus.SC_OK) {
                log.error("处理[DELETE]请求失败，请求接口：【{}】,请求参数：【{}】，返回状态：【{}】，返回内容：【{}】", url, content, response.getReasonPhrase(), res);
            }
            return new Response(res,code);
        } catch (Exception e) {
            if (httpDelete != null) {
                httpDelete.abort();
            }
            throw new RuntimeException("HTTP请求失败:" + e.getMessage());
        }
    }

    /**
     * 文件上传
     *
     * @param file
     * @param url
     * @return
     */
    public static Response upload(MultipartFile file, String url) {
        HttpPost httpPost = null;
        try (CloseableHttpClient httpClient = getHttpClient()) {
            httpPost = new HttpPost(url);
            // 创建待处理的文件
            String fileName = file.getOriginalFilename();
            ContentBody files = new ByteArrayBody(file.getBytes(), fileName);

            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.addPart("file", files);
            HttpEntity entity = builder.build();
            httpPost.setEntity(entity);
            CloseableHttpResponse response = httpClient.execute(httpPost);
            HttpEntity responseEntity = response.getEntity();
            int code=response.getCode();
            log.info("process upload request, response Http Status：【{}】", code);
            if (responseEntity != null && code == HttpStatus.SC_OK) {
                return new Response(EntityUtils.toString(responseEntity, StandardCharsets.UTF_8));
            }
            return new Response(StringUtils.EMPTY);
        } catch (Exception e) {
            log.error("请求服务[{}]失败，错误原因：{}", url, e);
            throw new RuntimeException("HTTP请求失败:" + e.getMessage());
        }
    }

    /**
     * 下载文件
     *
     * @param url
     * @param fileLocation
     * @throws Exception
     */
    public static boolean download(String url, String fileLocation) {
        if (StringUtils.isBlank(url)) {
            throw new IllegalArgumentException("HTTP请求地址不能为空");
        }
        CloseableHttpResponse response = null;
        HttpGet httpGet = new HttpGet(url);
        try (CloseableHttpClient httpClient = getHttpClient(); FileOutputStream fileOutputStream = new FileOutputStream(fileLocation)) {
            response = httpClient.execute(httpGet);
            if (response.getCode() != HttpStatus.SC_OK) {
                throw new HttpException(response.getReasonPhrase());
            }
            HttpEntity httpEntity = response.getEntity();
            httpEntity.writeTo(fileOutputStream);
            fileOutputStream.flush();
        } catch (Exception e) {
            log.error("向 [{}]发送的到请求失败，失败原因{}", url, e);
            httpGet.abort();
            return Boolean.FALSE;
        }
        httpGet.abort();
        return Boolean.TRUE;
    }

    /**
     * 获取url
     *
     * @param url
     * @param content
     * @return
     */
    private static String getUrl(String url, String content) throws ParseException, IOException {
        if (!Strings.isNullOrEmpty(content)) {
            List<NameValuePair> pairs = getNameValuePair(content);
            String params = null;
            if (!CollectionUtils.isEmpty(pairs)) {
                params = EntityUtils.toString(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8));
            }
            return (url.contains("?") ? url + "&" : url + "?") + (Strings.isNullOrEmpty(params) ? "" : params);
        }
        return url;
    }

    /**
     * 获取 namevalue对
     *
     * @param content
     * @return
     * @throws IOException
     */
    private static List<NameValuePair> getNameValuePair(String content) {
        Map<String, String> dataMap = isMap(content);
        if (!CollectionUtils.isEmpty(dataMap)) {
            List<NameValuePair> pairs = new ArrayList<>();
            for (Map.Entry<String, String> entry : dataMap.entrySet()) {
                pairs.add(new BasicNameValuePair(entry.getKey(), getNoneNullString(entry.getValue())));
            }
            return pairs;
        }
        return null;
    }

    @Getter
    @AllArgsConstructor
    public enum ContentType {

        JSON("application/json"),
        NONE(""),
        FORM("application/x-www-form-urlencoded");

        private String content;

        @Override
        public String toString() {
            return content;
        }
    }

    @Getter
    @AllArgsConstructor
    public enum RequestMethodEnum {

        POST(HttpPost.METHOD_NAME),
        GET(HttpGet.METHOD_NAME),
        PUT(HttpPut.METHOD_NAME),
        PATCH(HttpPatch.METHOD_NAME),
        DELETE(HttpDelete.METHOD_NAME),
        HEAD(HttpHead.METHOD_NAME),
        OPTIONS(HttpOptions.METHOD_NAME),
        TRACE(HttpTrace.METHOD_NAME),
        ;

        private String method;

        public static RequestMethodEnum parse(String name) {
            if (Strings.isNullOrEmpty(name)) {
                throw new IllegalArgumentException("未知的HTTP请求方式");
            }
            for (RequestMethodEnum requestMethodEnum : RequestMethodEnum.values()) {
                if (requestMethodEnum.getMethod().equals(name.toUpperCase())) {
                    return requestMethodEnum;
                }
            }
            throw new IllegalArgumentException("未知的HTTP请求方式");
        }
    }

    /**
     * @param object 格式化对象
     * @param <T>    对象类型
     * @return
     */
    private static <T> String getNoneNullString(T object) {
        if (object == null) {
            return StringUtils.EMPTY;
        }
        if (object instanceof String) {
            return object.toString();
        } else if (object instanceof Integer) {
            return String.valueOf(object);
        } else if (object instanceof Long) {
            return String.valueOf(object);
        } else if (object instanceof BigDecimal) {
            return ((BigDecimal) object).setScale(2, RoundingMode.UNNECESSARY).toPlainString();
        } else if (object instanceof Date) {
            return date((Date) object, DEFAULT_FORMAT);
        }
        return String.valueOf(object);
    }

    /**
     * 日期格式化
     *
     * @param date
     * @param timeFormat
     * @return
     */
    public static String date(Date date, String timeFormat) {
        if (date == null) {
            return null;
        }
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(timeFormat);
        return date.toInstant().atZone(DEFAULT_ZONE).format(dateTimeFormatter);
    }

    public static Map isMap(String data) {
        if (Strings.isNullOrEmpty(data)) {
            return null;
        }
        try {
            return com.alibaba.fastjson.JSON.parseObject(data, Map.class);
        } catch (JSONException jsonEx) {
            return null;
        }
    }


    public static CloseableHttpClient getHttpClient() {
        // 创建自定义的SSLContext，忽略所有SSL证书验证
        // SSLContext sslContext = SSLContextBuilder.create()
        //         .loadTrustMaterial((x509Certificates, s) -> true)
        //         .build();
        HttpClientHostnameVerifier httpClientHostnameVerifier = new HttpClientHostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }

            @Override
            public void verify(String host, X509Certificate cert) throws SSLException {
            }
        };

        // 使用 SSLContext 创建一个 HttpClient
        // SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, httpClientHostnameVerifier);
        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(SSLContexts.createDefault(), httpClientHostnameVerifier);

        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create()
                .register(URIScheme.HTTP.id, PlainConnectionSocketFactory.getSocketFactory())
                .register(URIScheme.HTTPS.id, sslSocketFactory)
                .build());

        return HttpClients.custom().setConnectionManager(poolingHttpClientConnectionManager).build();
    }
}